using System;
using System.Collections.Generic;
using UnityEngine;
using DanielSig.Geometry;
using DanielSig;

namespace DanielSig.UnityExtensions
{
	public interface IBitStreamSerializable
	{
		void Serialize(BitStream bitStream);
	}
	public static class XBitStream
	{
		public static void Serialize(this BitStream stream, ref String value)
		{
			int length = value.Length;
			stream.Serialize(ref length);
			char[] chars = new char[length];
			value.CopyTo(0, chars, 0, value.Length.Min(chars.Length));
			for(int i = 0; i < length; i++)
			{
				char ch = chars[i];
				stream.Serialize(ref ch);
				chars[i] = ch;
			}
			value = new String(chars);
		}
		public static void Serialize<T>(this BitStream stream, ref T value) where T : IBitStreamSerializable
		{
			value.Serialize(stream);
		}
		public static void Serialize<T>(this BitStream stream, T[] values) where T : IBitStreamSerializable
		{
			int length = values.Length;
			stream.Serialize(ref length);
			if(length != values.Length) Debug.LogError("Array length doesn't matach: " + length + " : " + values.Length);
			for(int i = 0; i < length; i++)
			{
				T value = values[i];
				stream.Serialize<T>(ref value);
				values[i] = value;
			}
		}
		public static void Serialize<T>(this BitStream stream, ref T[] values) where T : IBitStreamSerializable
		{
			int length = values.Length;
			stream.Serialize(ref length);
			if(values.Length == 0 && length != 0) values = new T[length];
			for(int i = 0; i < length; i++)
			{
				T value = values[i];
				stream.Serialize<T>(ref value);
				values[i] = value;
			}
		}
		public static void Serialize<T>(this BitStream stream, LinkedList<T> values) where T : IBitStreamSerializable
		{
			int length = values.Count;
			stream.Serialize(ref length);
			while(values.Count < length)
			{
				values.AddLast(default(T));
			}
			while(values.Count > length)
			{
				values.RemoveLast();
			}
			if(values.Count > 0)
			{
				LinkedListNode<T> link = values.First;
				while(link != null)
				{
					T value = link.Value;
					stream.Serialize<T>(ref value);
					link.Value = value;
					link = link.Next;
				}
			}
		}
		public static void Serialize(this BitStream stream, ref double value)
		{
			if(stream.isWriting)
			{
				byte[] bytes = BitConverter.GetBytes(value);
				int val1 = BitConverter.ToInt32(bytes, 4);
				int val2 = BitConverter.ToInt32(bytes, 0);
				stream.Serialize(ref val1);
				stream.Serialize(ref val2);
			}
			else
			{
				int val1 = 0;
				int val2 = 0;
				stream.Serialize(ref val1);
				stream.Serialize(ref val2);
				
				
				byte[] bytes = new byte[8];
				BitConverter.GetBytes(val1).CopyTo(bytes, 4);
				BitConverter.GetBytes(val2).CopyTo(bytes, 0);
				
				value = BitConverter.ToDouble(bytes, 0);
			}
		}
		public static void Serialize(this BitStream stream, ref UnitVector16 value, byte[] other16bits)
		{
			if(other16bits.Length != 2)
				throw new ArgumentOutOfRangeException("The byte array other16bits must contain only 2 bytes");
			
			if(stream.isWriting)
			{
				byte[] bytes = new byte[4];
				BitConverter.GetBytes(value.rawData).CopyTo(bytes, 2);
				other16bits.CopyTo(bytes, 0);
				
				int val = BitConverter.ToInt32(bytes, 0);
				stream.Serialize(ref val);
			}
			else
			{
				int val = 0;
				stream.Serialize(ref val);
				
				
				byte[] bytes = BitConverter.GetBytes(val);
				value.rawData = BitConverter.ToUInt16(bytes, 2);
				other16bits[0] = bytes[0];
				other16bits[1] = bytes[1];
			}
		}
		public static void Serialize(this BitStream stream, byte[] bytes, int length)
		{
			if(bytes.Length < 4)
				Debug.LogWarning("byte[] bytes is less than 4 bytes in length," + 
								" some space is wasted by serializing such small amount of data." + 
								" It's recommended to use the Array.CopyTo() method to join byte arrays" + 
								" together so that the length will be at least 4 bytes.");
			if(stream.isWriting)
			{
				for(int i = 0; i < length; i+=4)
				{
					int val = BitConverter.ToInt32(bytes, i);
					stream.Serialize(ref val);
				}
			}
			else
			{
				int val = 0;
				for(int i = 0; i < length; i+=4)
				{
					stream.Serialize(ref val);
					BitConverter.GetBytes(val).CopyTo(bytes, i);
				}
			}
		}
		/**
		 * difference between Serialize(ref byte[] byte) and Serialize(byte[] byte)
		 * is that the former supports a veriable number of bytes
		 */
		public static void Serialize(this BitStream stream, ref byte[] bytes)
		{
			if(stream.isWriting)
			{
				int length = bytes.Length;
				stream.Serialize(ref length);
				stream.Serialize(bytes, length);
			}
			else
			{
				int length = 0;
				stream.Serialize(ref length);
				Array.Resize<byte>(ref bytes, length);
				stream.Serialize(bytes, length);
			}
		}
		/**
		 * difference between Serialize(ref byte[] byte) and Serialize(byte[] byte)
		 * is that the former supports a veriable number of bytes
		 */
		public static void Serialize(this BitStream stream, byte[] bytes)
		{
			stream.Serialize(bytes, bytes.Length);
		}
		public static void Serialize(this BitStream stream, byte[] firstTwoBytes, byte[] secondTwoBytes)
		{
			if(firstTwoBytes.Length != 2)
				throw new ArgumentOutOfRangeException("The byte array firstTwoBytes must contain only 2 bytes");
			if(secondTwoBytes.Length != 2)
				throw new ArgumentOutOfRangeException("The byte array secondTwoBytes must contain only 2 bytes");
			
			if(stream.isWriting)
			{
				int val = BitConverter.ToInt32(firstTwoBytes, 0);
				val |= BitConverter.ToInt32(secondTwoBytes, 0) << 16;
				stream.Serialize(ref val);
			}
			else
			{
				int val = 0;
				stream.Serialize(ref val);
				
				byte[] bytes = BitConverter.GetBytes(val);
				firstTwoBytes[0] = bytes[0];
				firstTwoBytes[1] = bytes[1];
				secondTwoBytes[0] = bytes[2];
				secondTwoBytes[1] = bytes[3];
			}
		}
		public static void Serialize(this BitStream stream, uint[] values)
		{
			uint maxPowerOfTwo;
			uint bitIndex = 0;
			if(stream.isWriting)
			{
				maxPowerOfTwo = 1;
				for(int i = 0; i < values.Length; i++)
				{
					uint val = values[i];
					uint nextPowOf2 = PowerOfTwo(val);
					if(nextPowOf2 > maxPowerOfTwo)  maxPowerOfTwo = nextPowOf2;
				}
				byte[] bytes = new byte[((5 + values.Length * maxPowerOfTwo) >> 3).Max(1)];
				bytes.Write(maxPowerOfTwo, ref bitIndex, 5);
				for(int i = 0; i < values.Length; i++)
				{
					bytes.Write(values[i], ref bitIndex, (int)maxPowerOfTwo);
				}
				stream.Serialize(ref bytes);
			}
			else
			{
				byte[] bytes = new byte[0];
				maxPowerOfTwo = 0;
				
				stream.Serialize(ref bytes);
				bytes.Read(ref maxPowerOfTwo, ref bitIndex, 5);
				
				for(int i = 0; i < values.Length; i++)
				{
					bytes.Read(ref values[i], ref bitIndex, (int)maxPowerOfTwo);
				}
			}
		}
		private static uint PowerOfTwo(uint value)
		{
			int shift = 16;
			int shiftLevel = 16;
			for(int i = 0; i < 5; i++)
			{
				shiftLevel >>= 1;
				if((value >> shift) > 0) shift += shiftLevel;
				else 					 shift -= shiftLevel;
			}
			return (uint)shift;
		}
		public const uint halfMax = uint.MaxValue >> 1;
		
		
	}
}

